/**
 * 	Copyright 2011 rkehoe
 *
 * 	Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 * 	You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *	limitations under the License.
 *
 */
package queryable.store;

import java.sql.Connection;
import java.sql.Statement;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.h2.jdbcx.JdbcConnectionPool;
import org.h2.tools.DeleteDbFiles;

import queryable.store.sql.HybridHashMapStore;
import queryable.store.sql.ISQLGenerator;
import queryable.store.sql.IStorageProvider;
import queryable.store.sql.JDBCStore;
import queryable.store.sql.SQLGeneratorH2DB;

/**
 * Provides static factory methods for H2 based {@link IStore}.
 * 
 * @author rkehoe
 * 
 */
public class Stores
{
	/**
	 * Factory for simple Store with default in-memory JDBC store.
	 * The entity object is Serialized and stored within the JDBC
	 * store.
	 * 
	 * @param <T>
	 * @param name the name of the store
	 * @param domainObjectClass
	 *            - the class of the domain object
	 * @return DAO
	 * @throws Exception
	 */
	public static <K> IStore<K> memoryStore(String name, Class<K> domainObjectClass) throws Exception
	{
		final DataSource dataSource = Stores.getMemoryDataSource(name);
		IStorageProvider storage = new StorageProvider(dataSource);
		IStore<K> dao = new JDBCStore<K>(name, domainObjectClass,storage);
		return dao;
	}

	/**
	 * Provides a 'pure' memory based store where the object is 
	 * stored within a local HashMap and the object fields stored
	 * in a within-memory JDBC DB.
	 * 
	 * @param name
	 * @param domainObjectClass
	 * @return
	 * @throws Exception
	 */
	public static <K> IStore<K> hybridMemoryStore(String name, Class<K> domainObjectClass) throws Exception
	{
		final DataSource dataSource = Stores.getMemoryDataSource(name);
		IStorageProvider storage = new StorageProvider(dataSource);
		IStore<K> dao = new HybridHashMapStore<K>(name, domainObjectClass,storage,new HashMap<Object,K>());
		return dao;
	}

	/**
	 * Provides a 'pure' memory based store where the object is 
	 * stored within a local HashMap and the object fields stored
	 * in a within-memory JDBC DB.
	 * 
	 * @param name
	 * @param domainObjectClass
	 * @param map
	 * @return
	 * @throws Exception
	 */
	public static <K> IStore<K> hybridMemoryStore(String name, Class<K> domainObjectClass, Map<Object,K> map) throws Exception
	{
		final DataSource dataSource = Stores.getMemoryDataSource(name);
		IStorageProvider storage = new StorageProvider(dataSource);
		IStore<K> dao = new HybridHashMapStore<K>(name, domainObjectClass,storage,map);
		return dao;
	}

	/**
	 * Factory for simple Store with default in-memory JDBC store.
	 * The entity object is Serialized and stored within the JDBC
	 * store.
	 * @param domainObjectClass
	 * @return
	 * @throws Exception
	 */
	public static <K> IStore<K> memoryStore(Class<K> domainObjectClass) throws Exception
    {
	    return Stores.memoryStore(null, domainObjectClass);
    };


	/**
	 * Factory for simple Store with disk based storage
	 * 
	 * @param <T>
	 * @param name of the store
	 * @param domainObjectClass
	 *            - the class of the domain object
	 * @return DAO
	 * @throws Exception
	 */
	public static <K> IStore<K> diskStore(String name, Class<K> domainObjectClass) throws Exception
	{
		DataSource dataSource = Stores.getDataSource(".");
		IStorageProvider storage = new StorageProvider(dataSource);
		IStore<K> dao = new JDBCStore<K>(name, domainObjectClass,storage);
		return dao;
	}

	/**
	 * This example uses H2 DB.
	 * 
	 * If you need to persist and retrieve arbitrary domain objects then use a
	 * file based DataSource.
	 * 
	 * Returns a DataSource for an in-memory DB. 
	 * @param name 
	 * @return a memory based DataSource
	 * @throws Exception
	 */
	private static DataSource getMemoryDataSource(String name) throws Exception
	{
		Class.forName("org.h2.Driver");
		/*
		 * If you use this in-memory database then you have a relatively
		 * efficient tool to dice-and-slice large data-sets of domain objects
		 * without having to hard code complex comparators etc, etc,..
		 */
		DataSource ds = JdbcConnectionPool.create("jdbc:h2:mem:ObjectStore-"+name, "sa", "sa");
		return ds;
	}

	/**
	 * Returns a DataSource for an disk based DB. 
	 * @return a memory based DataSource
	 * @throws Exception
	 */
	private static DataSource getDataSource(String dirPath) throws Exception
	{
		Class.forName("org.h2.Driver");
		DataSource ds = null;

		if (dirPath == null || dirPath.isEmpty())
		{
			DeleteDbFiles.execute("~", "ObjectStore", true);
			ds = JdbcConnectionPool.create("jdbc:h2:~/ObjectStore", "sa", "sa");
		}
		else
		{
			DeleteDbFiles.execute(dirPath, "ObjectStore", true);
			ds = JdbcConnectionPool.create("jdbc:h2:" + dirPath + "/ObjectStore", "sa", "sa");
		}

		return ds;
	}

	public static class StorageProvider implements IStorageProvider
	{

		private ISQLGenerator sqlGeneratorH2DB;
		private DataSource dataSource;

		/**
		 * @throws Exception 
         * 
         */
        public StorageProvider(DataSource dataSource) throws Exception
        {
        	this.dataSource = dataSource;
			sqlGeneratorH2DB = new SQLGeneratorH2DB();
        }
        
		
		@Override
		public ISQLGenerator getGenerator() throws Exception
		{
			return sqlGeneratorH2DB;
		}

		@Override
		public DataSource getDataSource() throws Exception
		{
			return dataSource;
		}

		@Override
		public void createTable(List<IFieldMetadata> fields, String tableName) throws Exception
		{
			Connection connection = null;
			try
			{
				ISQLGenerator generator = this.getGenerator();
				String propertiesTableDDL = generator.getTableDDL(fields, tableName);
				System.out.println("Creating table: " + propertiesTableDDL);

				connection = this.getDataSource().getConnection();
				Statement statement = connection.createStatement();
				statement.execute(propertiesTableDDL);
			}
			finally
			{
				if (connection != null)
				{
					connection.close();
				}
			}
		}

		@Override
		public void createIndex(String tableName, String indexName, String... columns ) throws Exception
		{
			Connection connection = null;
			try
			{
				connection = this.getDataSource().getConnection();
				ISQLGenerator generator = this.getGenerator();

				List<String> indexDDL = generator.getIndexDDL(tableName, indexName, columns);
				for (String string : indexDDL)
				{
					System.out.println("Creating index: " + indexDDL);
					Statement statement = connection.createStatement();
					statement.execute(string);
				}
			}
			finally
			{
				if (connection != null)
				{
					connection.close();
				}
			}
		}

		@Override
        public void createView(String viewName, String selectStmt) throws Exception
        {
			Connection connection = null;
			try
			{
				connection = this.getDataSource().getConnection();
				Statement statement = connection.createStatement();
				String sql = "create view if not exists " + viewName + " as " + selectStmt;
				System.out.println("Creating view : " + sql);
				statement.execute(sql);
			}
			finally
			{
				if (connection != null)
				{
					connection.close();
				}
			}
        }
	}
}
